package server;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import data.Invite;
import database.DataHandler;

import mockup.AppointmentMockup;
import mockup.InviteMockup;
import mockup.LoginMockup;
import mockup.MeetingMockup;
import mockup.MessageMockup;
import mockup.Mockup;
import mockup.ReservationMockup;
import mockup.RoomMockup;
import mockup.UserMockup;

/**
 * Processes mockups and performs the necessary action associated with the request
 * @author Erik
 *
 */
public class MockupProcessor {

	/**
	 * Processes the mockup and returns the appropriate response
	 * @param mockup The mockup to process
	 * @return The response
	 */
	public static Serializable process(Mockup mockup, Session session) {
		//check that the login is valid
		if(!validLogin(session)) {
			return new LoginMockup(-1, null);
		}
		
		//check that the mockup fits the session
		if(mockup.getSessionId() != session.getSessionID())
			return new Mockup(-1);
		
		int mockupState = mockup.getState();
		
		//if the operation is to get a list of a specific users appointments and meetings; comply
		if(mockupState == Mockup.GET_APPOINTMENTS) {
			if(mockup instanceof UserMockup) {
				return Server.getUsersAppointments(mockup.getId());
			}
			else
				return new Mockup(-1);
		}
		if(mockupState == Mockup.GET_MEETINGS) {
			if(mockup instanceof UserMockup) {
				return Server.getUsersMeetings(mockup.getId());
			}
			else
				return new Mockup(-1);
		}
		
		//if the operation is to get a list of available rooms
		if(mockupState == Mockup.GET_ROOMS) {
			if(mockup instanceof ReservationMockup)
				return Server.getAvailableRooms((ReservationMockup)mockup);
			else
				return new Mockup(-1);
		}
		
		//if the operation is to get updates, return the updates
		if(mockupState == Mockup.GET_CREATED) {
			ArrayList<Mockup> list = Server.createUpdates.get(session.getUser().getId());
			Server.createUpdates.put(session.getUser().getId(), new ArrayList<Mockup>());
			return list;
		}
		
		//if the operation is to get updates, return the updates
		if(mockupState == Mockup.GET_REMOVED) {
			ArrayList<Mockup> list = Server.deleteUpdates.get(session.getUser().getId());
			Server.deleteUpdates.put(session.getUser().getId(), new ArrayList<Mockup>());
			return list;
		}
		
		//if the operation is to get updates, return the updates
		if(mockupState == Mockup.GET_CHANGED) {
			ArrayList<Mockup> list = Server.changeUpdates.get(session.getUser().getId());
			Server.changeUpdates.put(session.getUser().getId(), new ArrayList<Mockup>());
			return list;
		}
		
		//the user can always get an object
		if(mockupState == Mockup.GET) {
			// Meeting
			if(mockup instanceof MeetingMockup) {
				return Server.meetings.get(mockup.getId());
			}
			
			// Appointment
			else if(mockup instanceof AppointmentMockup) {
				return Server.appointments.get(mockup.getId());
			} 
			
			// Message
			else if(mockup instanceof MessageMockup) {
				if(((MessageMockup)mockup).getReceiver() == session.getUser().getId())
					return Server.messages.get(mockup.getId());
				else
					return new Mockup(-1);
			}
			
			// Room
			else if(mockup instanceof RoomMockup) {
				return Server.rooms.get(mockup.getId());
			} 
			
			// User
			else if(mockup instanceof UserMockup) {
				return Server.users.get(mockup.getId());
			}
			
			// Something else
			else return new Mockup(-1);
		}
		
		// CREATE
		else if(mockupState == Mockup.CREATE) {
			// Meeting
			if(mockup instanceof MeetingMockup) {
				MeetingMockup meeting = (MeetingMockup) mockup;
				if(Server.meetings.containsKey(meeting.getId()) || 
						!checkReservation(meeting.getReservation(), null) ||
						!checkParticipants(meeting) ||
						meeting.getReservation().getAppointment() != meeting)
					return new Mockup(-1);
				
				meeting.setOwnerID(session.getUser().getId());
				
				//refuse to create meetings having a duration under 5 minutes
				if(meeting.getReservation().getEndTime() - meeting.getReservation().getStartTime() < 1000*60*5)
					return new Mockup(-1);
				
				insert(meeting.getReservation());
				insert(meeting);

				return new Mockup(meeting.getId());
			} 
			
			// Appointment
			else if(mockup instanceof AppointmentMockup) {
				AppointmentMockup appointment = (AppointmentMockup) mockup;
				if(Server.appointments.containsKey(appointment.getId()) || 
						!checkReservation(appointment.getReservation(),null) ||
						appointment.getReservation().getAppointment() != appointment)
					return new Mockup(-1);
				
				//refuse to create meetings having a duration under 5 minutes
				if(appointment.getReservation().getEndTime() - appointment.getReservation().getStartTime() < 1000*60*5)
					return new Mockup(-1);
				
				appointment.setOwnerID(session.getUser().getId());
				
				insert(appointment.getReservation());
				insert(appointment);
				
				return new Mockup(appointment.getId());
			} 
			
			else {
				return new Mockup(-1);
				
			}
		}
		
		// DELETE
		else if(mockupState == Mockup.DELETE) {
			
			// Meeting
			if(mockup instanceof MeetingMockup) {
				MeetingMockup meeting = (MeetingMockup) mockup;
				if(!Server.meetings.containsKey(meeting.getId()) ||
						Server.meetings.get(meeting.getId()).getOwnerID() != session.getUser().getId())
					return new Mockup(-1);
				
				delete(meeting);
				delete(meeting.getReservation());
				
				return new Mockup(mockup.getId());
			} 
			
			// Appointment
			else if(mockup instanceof AppointmentMockup) {
				AppointmentMockup appointment = (AppointmentMockup) mockup;
				if(!Server.appointments.containsKey(appointment.getId()) ||
						Server.appointments.get(appointment.getId()).getOwnerID() != session.getUser().getId())
					return new Mockup(-1);
				
				delete(appointment);
				delete(appointment.getReservation());
				
				return new Mockup(mockup.getId());
			} 
			
			// Message
			else if(mockup instanceof MessageMockup) {
				MessageMockup message = (MessageMockup) mockup;
				if(!Server.messages.containsKey(message.getId()) ||
						Server.messages.get(message.getId()).getReceiver() != session.getUser().getId())
					return new Mockup(-1);
				
				delete(message);
			}
			
			else {
				return new Mockup(-1);
			}
		}
		
		// CHANGE
		else if(mockupState == Mockup.CHANGE) {
			if(Server.debugMiddleLogic)
				System.out.println("MockupProcessor::process: starting update evaluation of a " + mockup.getClass().getSimpleName());
			// Meeting
			if(mockup instanceof MeetingMockup) {
				MeetingMockup newMeeting = (MeetingMockup) mockup;
				if(!Server.meetings.containsKey(newMeeting.getId()) ||
						Server.meetings.get(newMeeting.getId()).getOwnerID() != session.getUser().getId())
					return new Mockup(-1);
				
				MeetingMockup oldMeeting = Server.meetings.get(newMeeting.getId());
				ReservationMockup oldres = oldMeeting.getReservation();
				ReservationMockup newres = newMeeting.getReservation();
				
				if(!checkReservation(newres, oldres))
					return new Mockup(-1);

				//refuse to create meetings having a duration under 5 minutes
				if(newres.getEndTime() - newres.getStartTime() < 1000*60*5)
					return new Mockup(-1);

				newMeeting.setOwnerID(oldMeeting.getOwnerID());
				newres.setId(oldres.getId());
				
				if(Server.debugHigherLogic)
					System.out.println("MockupProcessor::process: updated a " + mockup.getClass().getSimpleName());
				if(newres.getRoomID() > 0)
					if(!Server.rooms.containsKey(newres.getRoomID()))
						return new Mockup(-1);
				
				DataHandler.reservation.update(newres);
				DataHandler.meeting.update(newMeeting);
				Server.reservations.put(newres.getId(), newres);
				Server.meetings.put(newMeeting.getId(), newMeeting);
				
				// set answer for all invites to this meeting to ANSWER_UNKNOWN
				for(MessageMockup message: Server.messages.values()) {
					if(!(message instanceof InviteMockup))
						continue;
					InviteMockup invite = (InviteMockup) message;
					if(invite.getMeeting() == newMeeting.getId()) {
						invite.setAnswer(InviteMockup.ANSWER_UNKOWN);
						DataHandler.message.update(invite);
						for(int participant: oldMeeting.getParticipants())
							sendIfLoggedOn(invite, participant, Mockup.CHANGE);
						sendIfLoggedOn(invite, newMeeting.getOwnerID(), Mockup.CHANGE);
					}
				}				
				
				//update participants list in database
				ArrayList<Integer> oldParticipants = oldMeeting.getParticipants();
				ArrayList<Integer> newParticipants = newMeeting.getParticipants();
				
				//remove all participants that are gone
				for(int userId : oldParticipants) {
					if(!newParticipants.contains(userId)) {
						//user is no longer invited
						
						DataHandler.meeting.removeParticipant(oldMeeting.getId(), userId);
						
						//remove users messages for this meeting
						for(MessageMockup message : Server.messages.values()) {
							if(message instanceof InviteMockup && ((InviteMockup)message).getReceiver() == userId && 
									((InviteMockup)message).getMeeting() == oldMeeting.getId()) {
								delete(message);
							}
						}
						
						//notify the user that it is removed from the meeting
						MessageMockup message = new MessageMockup(-1, oldMeeting.getId(), userId, 
								oldMeeting.getTitle(), "Du er ikke lenger invitert til møtet", 
								System.currentTimeMillis(), 0);
						insert(message);
					}
					else {
						// user is still invited, notify of change
						MessageMockup message = new MessageMockup(-1, oldMeeting.getId(), userId, oldMeeting.getTitle(), 
								"Møtet er endret", System.currentTimeMillis(), 0);
						insert(message);						
					}
					
				}
				//add all new participants 
				for(int userId: newParticipants) {
					if(!oldParticipants.contains(userId)) {
						DataHandler.meeting.addParticipant(oldMeeting.getId(), userId);
						
						//invite user
						InviteMockup invite = createInvite(newMeeting, userId);
						insert(invite);
					}
				}
				
				
				return new Mockup(newMeeting.getId());
			} 
			
			// Appointment
			else if(mockup instanceof AppointmentMockup) {
				System.out.println("entering appointment, session id = " + session.getSessionID());
				AppointmentMockup appointment = (AppointmentMockup) mockup;
				if(!Server.appointments.containsKey(appointment.getId()) ||
						Server.appointments.get(appointment.getId()).getOwnerID() != session.getUser().getId())
					return new Mockup(-1);
				System.out.println("passed initial test");
				AppointmentMockup oldapp = Server.appointments.get(appointment.getId());
				ReservationMockup oldres = oldapp.getReservation();
				ReservationMockup newres = appointment.getReservation();
				
				if(!checkReservation(newres, oldres))
					return new Mockup(-1);
				System.out.println("passed reservation test");
				
				//refuse to create meetings having a duration under 5 minutes
				if(newres.getEndTime() - newres.getStartTime() < 1000*60*5)
					return new Mockup(-1);
				
				oldres.setEndTime(appointment.getReservation().getEndTime());
				oldres.setStartTime(appointment.getReservation().getStartTime());
				
				if(newres.getRoomID() > 0) {
					if(Server.rooms.containsKey(newres.getRoomID()))
						oldres.setRoomID(appointment.getReservation().getRoomID());
					else
						return new Mockup(-1);
				} else
					oldres.setRoomID(0);
				
				System.out.println("found and set room " + oldres.getRoomID());
				
				
				oldapp.setDescription(appointment.getDescription());
				oldapp.setLocation(appointment.getLocation());
				oldapp.setTitle(appointment.getTitle());
				
				DataHandler.reservation.update(oldres);
				DataHandler.appointment.update(oldapp);

				if(Server.debugHigherLogic)
					System.out.println("MockupProcessor::process: updated a " + mockup.getClass().getSimpleName());
				
				return new Mockup(oldapp.getId());
				
			}
			
			// Invite
			else if(mockup instanceof InviteMockup) { 
				InviteMockup invite = (InviteMockup) mockup;
				if(!Server.messages.containsKey(invite.getId()) ||
						Server.messages.get(invite.getId()).getReceiver() != session.getUser().getId())
					return new Mockup(-1);
				InviteMockup old = (InviteMockup) Server.messages.get(invite.getId());
				
				old.setAnswer(invite.getAnswer());
				old.setReadTime(invite.getReadTime());
				DataHandler.message.update(old);
				
				MeetingMockup meeting = (MeetingMockup) Server.meetings.get(old.getMeeting());
				
				if(meeting != null) {
					for(int userId : meeting.getParticipants())
						if(userId != session.getUser().getId())
							sendIfLoggedOn(old, userId, Mockup.CHANGE);
					sendIfLoggedOn(old, meeting.getOwnerID(), Mockup.CHANGE);
				} else {
					return new Mockup(-1);
				}
				
				if(Server.debugHigherLogic)
					System.out.println("MockupProcessor::process: updated a " + mockup.getClass().getSimpleName());
				
				return new Mockup(old.getId());
			}
			
			// Message
			else if(mockup instanceof MessageMockup) { 
				MessageMockup message = (MessageMockup) mockup;
				if(!Server.messages.containsKey(message.getId()) ||
						Server.messages.get(message.getId()).getReceiver() != session.getUser().getId())
					return new Mockup(-1);
				
				MessageMockup old = Server.messages.get(message.getId());
				
				old.setReadTime(message.getReadTime());
				DataHandler.message.update(old);
				
				if(Server.debugHigherLogic)
					System.out.println("MockupProcessor::process: updated a " + mockup.getClass().getSimpleName());
				
				return new Mockup(old.getId());
			}
			
			// User
			else if(mockup instanceof UserMockup) {
				UserMockup user = (UserMockup) mockup;
				if(!Server.users.containsKey(user.getId()) ||
						Server.users.get(user.getId()).getId() != session.getUser().getId())
					return new Mockup(-1);
				
				UserMockup old = Server.users.get(user.getId());
				user.setId(old.getId());
				
				Server.users.put(user.getId(), user);
				
				for(List<Mockup> l : Server.changeUpdates.values())
					l.add(user);
				
				DataHandler.user.update(user);
				
				if(Server.debugHigherLogic)
					System.out.println("MockupProcessor::process: updated a " + mockup.getClass().getSimpleName());
				
				return new Mockup(user.getId());
				
			} else {
				return new Mockup(-1);
			}
		}
		else
			return new Mockup(-1);
		
		System.err.println("MockupProcessor::processMockup: reached default return statement at end of method");
		return new Mockup(-1);
	}

	private static boolean checkParticipants(MeetingMockup mockup) {
		for(int id : mockup.getParticipants())
			if(!Server.users.containsKey(id))
				return false;
		return true;
	}

	private static boolean checkReservation(ReservationMockup reservation, ReservationMockup excluding) {
		if(reservation.getAppointment() == null || reservation.getAppointment().getReservation() != reservation)
			return false;
		
		if(reservation.getRoomID() > 0) {			
			for(ReservationMockup res : Server.reservations.values()) {
				if(res != excluding)
					if(reservation.getRoomID() == res.getRoomID()) //same room
						
						if((res.getStartTime() <= reservation.getEndTime() && res.getStartTime() >= reservation.getStartTime()) ||
								(res.getEndTime() <= reservation.getEndTime() && res.getEndTime() >= reservation.getStartTime()) ||
								(res.getStartTime() <= reservation.getStartTime() && res.getEndTime() >= reservation.getEndTime()))
							return false;
			}
		}
		
		return true;
	}
	
	public static boolean validLogin(Session session) {
		return (session.getUser() != null && Server.sessions.containsKey(session.getSessionID()) && Server.users.containsKey(session.getUser().getId()));
	}
	
	private static InviteMockup createInvite(MeetingMockup meeting, int userId) {
		InviteMockup invite = new InviteMockup(-1, meeting.getId());
		invite.setAnswer(Invite.ANSWER_UNKOWN);
		invite.setDescription(meeting.getDescription());
		invite.setSendTime(System.currentTimeMillis());
		invite.setReadTime(0);
		invite.setSubject(meeting.getTitle());
		invite.setReceiver(userId);
		
		return invite;
	}
	
	private static void sendIfLoggedOn(Mockup mockup, int userId, int mockupState) {
		Map<Integer,ArrayList<Mockup>>  map = null;
		switch(mockupState) {
		case Mockup.CHANGE: map = Server.changeUpdates; break;
		case Mockup.CREATE: map = Server.createUpdates; break;
		case Mockup.DELETE: map = Server.deleteUpdates; break;
		default: System.err.println("MockupProcessor::sendIfLoggedOn: wrong mockupState"); return;
		}
		
		if(map.containsKey(userId))
			map.get(userId).add(mockup);
		
		return;
	}
	

	/**
	 * Inserts the mockup into the database and the server list, 
	 * in addition to sending the change to participants and recipients.
	 * @param mockup
	 */
	private static void insert(Mockup mockup) {
		
		if(mockup instanceof MeetingMockup) {
			MeetingMockup meeting = (MeetingMockup) mockup;
			meeting.setId(DataHandler.meeting.insert(meeting));
			Server.meetings.put(meeting.getId(), meeting);
			
			for (int userId : meeting.getParticipants()) {
				sendIfLoggedOn(meeting, userId, Mockup.CREATE);
				InviteMockup invite = createInvite(meeting, userId);
				invite.setId(DataHandler.message.insert(invite));
				Server.messages.put(invite.getId(), invite);
				for(int receiver : meeting.getParticipants()) {		
					sendIfLoggedOn(invite, receiver, Mockup.CREATE);
				}
				sendIfLoggedOn(invite, meeting.getOwnerID(), Mockup.CREATE);
			}
		}
		else if(mockup instanceof AppointmentMockup) {
			AppointmentMockup appointment = (AppointmentMockup) mockup;
			appointment.setId(DataHandler.appointment.insert(appointment));
			Server.appointments.put(appointment.getId(), appointment);
		}
		else if(mockup instanceof MessageMockup) {
			MessageMockup message = (MessageMockup) mockup;
			message.setId(DataHandler.message.insert(message));
			Server.messages.put(message.getId(), message);
			sendIfLoggedOn(message, message.getReceiver(), Mockup.CREATE);
		}
		else if(mockup instanceof ReservationMockup) {
			ReservationMockup reservation = (ReservationMockup) mockup;
			reservation.setId(DataHandler.reservation.insert(reservation));
			Server.reservations.put(reservation.getId(), reservation);
		}
		else
			System.err.println("MockupProcessor::insertIntoDatabase: The class " + mockup.getClass().getSimpleName() + " is unsupported");
		
		if(Server.debugHigherLogic)
			System.out.println("MockupProcessor::insert: creating a " + mockup.getClass().getSimpleName() + " with id="+mockup.getId());
	}
	
	/**
	 * Removes the mockup from the database and the server list, 
	 * and sends the change to participants and recipients.
	 * @param mockup
	 */
	private static void delete(Mockup mockup) {

		if(mockup instanceof MeetingMockup){
			MeetingMockup meeting = (MeetingMockup) mockup;
			DataHandler.meeting.delete(meeting);
			Server.meetings.remove(meeting.getId());
			
			//notify participants of removal
			for(int userId : meeting.getParticipants()) {
				//send message to user
				MessageMockup message = new MessageMockup(-1, meeting.getId(), userId, meeting.getTitle(), 
						"Møtet er avlyst", System.currentTimeMillis(), 0);
				insert(message);	
			}
			
			//remove invites for meeting
			ArrayList<InviteMockup> list = new ArrayList<InviteMockup>();
			for(MessageMockup msg : Server.messages.values()) {
				if(msg instanceof InviteMockup && msg.getMeeting() == meeting.getId()) {
					list.add((InviteMockup)msg);
				}
			}
			for(InviteMockup msg : list) {
				delete(msg);
			}
			
			//remove from participants
			for(int userId : meeting.getParticipants()) {
				sendIfLoggedOn(meeting, userId, Mockup.DELETE);
			}
			
		}
		else if(mockup instanceof AppointmentMockup){
			AppointmentMockup appointment = (AppointmentMockup) mockup;
			DataHandler.appointment.delete(appointment);
			Server.appointments.remove(appointment.getId());
		}
		else if(mockup instanceof MessageMockup) {
			MessageMockup message = (MessageMockup) mockup;
			DataHandler.message.delete(message);
			Server.messages.remove(message.getId());
		}
		else if(mockup instanceof ReservationMockup) {
			ReservationMockup reservation = (ReservationMockup) mockup;
			DataHandler.reservation.delete(reservation);
			Server.reservations.remove(reservation.getId());
		}
		else
			System.err.println("MockupProcessor::deleteFromDatabase: The class " + mockup.getClass().getName() + " is unsupported");

		if(Server.debugHigherLogic)
			System.out.println("MockupProcessor::insert: deleting a " + mockup.getClass().getName() + " with id="+mockup.getId());
	}
}
